package snowflake

import (
	"errors"
	"sync/atomic"
	"time"
)

const (
	timestampBits         = 42                  // 时间戳占用位数
	workerIDBits          = 10                  // 机器id所占位数
	sequenceBits          = 12                  // 序列所占的位数
	workerIDMax    uint16 = 1<<workerIDBits - 1 // 支持的最大机器id数量
	sequenceMax    uint16 = 1<<sequenceBits - 1 // 支持的最大序列id数量
	timestampMax   uint64 = 1<<timestampBits - 1
	workerIDShift         = sequenceBits                // 机器id左移位数
	timestampShift        = sequenceBits + workerIDBits // 时间戳左移位数

)

var epoch int64

func SetEpoch(date time.Time) {
	atomic.StoreInt64(&epoch, date.UnixNano())
}

func init() {
	// 2000.4.1
	SetEpoch(time.Date(2000, 4, 1, 0, 0, 0, 0, time.UTC))
}

func UnWrapperNextID(id uint64, _ error) uint64 {
	return id
}

type Snowflake struct {
	WorkerID  uint16
	timestamp atomic.Int64
	sequence  atomic.Uint32
}

func (s *Snowflake) Resolver(timestamp int64) uint16 {
	var (
		lastTimestamp int64
		seq, localSeq uint32
	)

	for {
		lastTimestamp = s.timestamp.Load()
		localSeq = s.sequence.Load()
		if lastTimestamp > timestamp {
			return sequenceMax
		}
		if lastTimestamp == timestamp {
			seq = uint32(sequenceMax) & (localSeq + 1)
			if seq == 0 {
				return sequenceMax
			}
		}

		if s.timestamp.CompareAndSwap(lastTimestamp, timestamp) && s.sequence.CompareAndSwap(localSeq, seq) {
			return uint16(seq)
		}
	}

}

func (s *Snowflake) NextID() (uint64, error) {
	timestamp := currentMillis()
	seq := s.Resolver(timestamp)

	// double-check
	for seq >= sequenceMax {
		timestamp = waitNextMills(timestamp)
		seq = s.Resolver(timestamp)
	}

	df := elapsedTime(timestamp)
	if df < 0 || uint64(df) > timestampMax {
		return 0, errors.New("snowflake: the maximum life cycle of the snowflake algorithm is 2^41-1(millis), please check start-time")
	}

	return (uint64(df) << uint64(timestampShift)) | (uint64(s.WorkerID) << uint64(workerIDShift)) | uint64(seq), nil
}

// built-in
func currentMillis() int64 {
	return time.Now().UnixNano() / 1e6
}

func waitNextMills(lastTimestamp int64) int64 {
	now := currentMillis()
	if now == lastTimestamp {
		now = currentMillis()
	}
	return now
}

func elapsedTime(current int64) int64 {
	return current - epoch/1e6
}

func New(workID uint16) (*Snowflake, error) {
	if workID > workerIDMax {
		return nil, errors.New("超过最大机器ID")
	}
	return &Snowflake{
		WorkerID: workID,
	}, nil
}
